package vip.xiaonuo.modular.business.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.java.Log;
import org.apache.commons.math3.stat.regression.OLSMultipleLinearRegression;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import vip.xiaonuo.core.dbs.CurrentDataSourceContext;
import vip.xiaonuo.modular.business.mapper.BiIndexTypeMapper;
import vip.xiaonuo.modular.business.param.StoreQueryParam;
import vip.xiaonuo.modular.business.service.RegressionAnalysisService;
import vip.xiaonuo.modular.cache.BaseDataRecordCache;
import vip.xiaonuo.modular.common.DistanceHelper;
import vip.xiaonuo.modular.common.WdAnalysisConst;
import vip.xiaonuo.modular.manage.bicouponwrite.entity.BiCouponWrite;
import vip.xiaonuo.modular.manage.bicouponwrite.service.BiCouponWriteService;
import vip.xiaonuo.modular.manage.biindexdata.entity.BiIndexData;
import vip.xiaonuo.modular.manage.biindexdata.mapper.BiIndexDataMapper;
import vip.xiaonuo.modular.manage.biindexdata.param.BiIndexDataParam;
import vip.xiaonuo.modular.manage.biindextype.entity.BiIndexType;
import vip.xiaonuo.modular.manage.bioperateddata.entity.BiOperatedData;
import vip.xiaonuo.modular.manage.bioperateddata.service.BiOperatedDataService;
import vip.xiaonuo.modular.manage.storeorder.mapper.BiStoreOrderMapper;
import vip.xiaonuo.modular.manage.storeorder.param.BiStoreOrderParam;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;

/**
 * 通过回归分析预测门店优惠券核销的重要经营因素
 */
@Service
@Log
public class RegressionAnalysisServiceImpl implements RegressionAnalysisService {

    @Resource
    private BiOperatedDataService biOperatedDataService;
    @Resource
    private BiIndexTypeMapper biIndexTypeMapper;
    @Resource
    private BiCouponWriteService biCouponWriteService;
    @Resource
    private BiIndexDataMapper biIndexDataMapper;

    @Resource
    private BaseDataRecordCache baseDataRecordCache;
    @Resource
    private BiStoreOrderMapper biStoreOrderMapper;


    /**
     * 1、优惠券核销影响因素分析：
     * 将单个门店的优惠券核销量（拉动效果）与指标数据进行回归，
     * 将显著性（p<0.1）的结果显示出来
     * 2、优惠券发放效果估计：
     * 与实际的核销量进行对比，判断结果是高于预测还是低于预测，然后显示分析结果。
     * @param param
     * @return
     */
    @Override
    public JSONObject couponAnalysisFactor(StoreQueryParam param) {
        Function<String,Double> function = storeId->{
            LambdaQueryWrapper<BiCouponWrite> couponWriteWrapper = new LambdaQueryWrapper<>();
            couponWriteWrapper.eq(BiCouponWrite::getWriteStoreId,storeId);
            CurrentDataSourceContext.setDataSourceType(WdAnalysisConst.CLICKHOUSE_DATASOURCE);
            List<BiCouponWrite> couponWriteList = biCouponWriteService.list(couponWriteWrapper);
            CurrentDataSourceContext.clearDataSourceType();
            //优惠券总金额
            double sum = 0;
            if (CollectionUtil.isNotEmpty(couponWriteList)) {
                sum = couponWriteList.stream().mapToDouble(el -> Double.parseDouble(el.getOrderAmount().toString())).sum();
            }
            return sum;
        };
        return this.processAnalysisFactor(param, "couponAnalysisFactor", function);
    }


    /**
     * 销售额影响因素分析
     * @return
     */
    public JSONObject calcTradeEffectFactor(StoreQueryParam param){
        Function<String,Double> function = storeId->{
            //总销售额
            BiStoreOrderParam orderParam = new BiStoreOrderParam();
            orderParam.setStoreCode(storeId);
            CurrentDataSourceContext.setDataSourceType(WdAnalysisConst.CLICKHOUSE_DATASOURCE);
            BigDecimal sumTradeAmount = biStoreOrderMapper.sumTradeAmount(orderParam);
            CurrentDataSourceContext.clearDataSourceType();
            double sum = 0;
            if (sumTradeAmount != null) {
                sum = sumTradeAmount.doubleValue();
            }
            return sum;
        };
        return this.processAnalysisFactor(param, "calcTradeEffectFactor", function);
    }

    /**
     * 处理店铺的某个数值（消费券总额或销售额） 与 自变量指标（道路网密度等）的因素分析
     * @param param
     * @param cacheKey
     * @param function
     * @return
     */
    public JSONObject processAnalysisFactor(StoreQueryParam param, String cacheKey, Function<String,Double> function) {
        JSONObject result = new JSONObject();
        //缓存计算结果,HashMap<String, Double> storeCouponMap,HashMap<String, double[]> storeSumMap,double[] estimates
        JSONObject cacheObject = baseDataRecordCache.get(cacheKey);
        HashMap<String, Double> yValueMap = null;
        HashMap<String, double[]> xValueMap = null;
        HashMap<String, BigDecimal> estimatesMap = null;
        double[] estimates = null;
        if (cacheObject != null) {
            log.info("读取缓存"+cacheKey);
            yValueMap = (HashMap<String, Double>) cacheObject.get("yValueMap");
            xValueMap = (HashMap<String, double[]>) cacheObject.get("xValueMap");
            estimates = (double[]) cacheObject.get("estimates");
            estimatesMap = (HashMap<String, BigDecimal>) cacheObject.get("estimatesMap");
        }else{
            // 1、查询所有门店
            String orgCode = param.getOrgCode();
            if (StringUtils.isEmpty(orgCode)) {
                log.info("没有传入商铺组织编号，默认使用：茶百道");
                orgCode = "chabaidao";
            }
            String cityName = param.getCityName();
            if (StringUtils.isEmpty(cityName)) {
                cityName = "武汉市";
            }

            log.info("获取商铺数据："+orgCode);
            LambdaQueryWrapper<BiOperatedData> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(BiOperatedData::getOrgCode,orgCode);
            wrapper.eq(BiOperatedData::getCityName,cityName);
            List<BiOperatedData> storeList = biOperatedDataService.list(wrapper);
            if (CollectionUtil.isEmpty(storeList)) {
                return result;
            }
            //2、遍历门店，统计销售额、道路网密度等自变量指标
            //确定有多少个自变量指标
            CurrentDataSourceContext.setDataSourceType(WdAnalysisConst.CLICKHOUSE_DATASOURCE);
            List<BiIndexType> biIndexTypes = biIndexTypeMapper.queryIndexTypeListByCK();
            CurrentDataSourceContext.clearDataSourceType();
            if (CollectionUtil.isEmpty(biIndexTypes)) {
                return result;
            }
            double[][] xData = new double[storeList.size()][biIndexTypes.size()];
            double[] yData = new double[storeList.size()];
            yValueMap = new HashMap<>();
            xValueMap = new HashMap<>();
            for (int i=0;i<storeList.size();i++) {
                BiOperatedData store = storeList.get(i);
                //调用传入的function，计算店铺的销售额或消费券等 应变量值
                double yValue = function.apply(store.getStoreId());
                yData[i] = yValue;
                yValueMap.put(store.getStoreId(), yValue);
                //计算自变量指标
                for(int j=0;j< biIndexTypes.size();j++){
                    BiIndexType type = biIndexTypes.get(j);
                    xData[i][j] = statisticsIndex(type,store);
                }
                xValueMap.put(store.getStoreId(),xData[i]);
                log.info("store:"+store.getStoreName()+" yValue:"+yValue);
            }
            //3、通过回归分析计算出每个指标的权重
            OLSMultipleLinearRegression regression = new OLSMultipleLinearRegression();
            regression.newSampleData(yData, xData);
            estimates = regression.estimateRegressionParameters();
            log.info("estimates:"+Arrays.toString(estimates));
            List<BiIndexType> sortedList = new ArrayList<>();
            for (int i = 0; i < biIndexTypes.size(); i++) {
                BiIndexType biIndexType = biIndexTypes.get(i);
                biIndexType.setMaxValue(new BigDecimal(estimates[i+1]).setScale(2,4));
                sortedList.add(biIndexType);
            }
            estimatesMap = new HashMap<>();
            sortedList.sort((e1,e2)->e2.getMaxValue().compareTo(e1.getMaxValue()));
            for (int i = 0; i < sortedList.size(); i++) {
                if (i >= 10) {
                    break;
                }
                BiIndexType biIndexType = sortedList.get(i);
                estimatesMap.put(biIndexType.getName(), biIndexType.getMaxValue());
            }
            log.info("estimatesMap:"+estimatesMap);
            //加入缓存
            cacheObject = new JSONObject();
            cacheObject.put("yValueMap", yValueMap);
            cacheObject.put("xValueMap", xValueMap);
            cacheObject.put("estimates", estimates);
            cacheObject.put("estimatesMap", estimatesMap);
            baseDataRecordCache.put(cacheKey, cacheObject,2*60*60L);
        }

        String storeCode = param.getStoreCode();
        log.info("影响因素分析,storeCode:"+storeCode);
        result.put("estimatesMap", estimatesMap);
        return this.predictEffectFactor(storeCode, result, yValueMap, xValueMap, estimates);
    }


    private JSONObject predictEffectFactor(String storeCode, JSONObject result, HashMap<String, Double> yValueMap, HashMap<String, double[]> xValueMap,
                                           double[] estimates){
        //4、与实际优惠券进行对比、判断结果高于还是低于预测
        Double aDouble = yValueMap.get(storeCode);
        double[] typeValues = xValueMap.get(storeCode);
        double predictValue = estimates[0];
        for (int i = 0; i < typeValues.length; i++) {
            predictValue += typeValues[i]*estimates[i+1];
        }
        log.info("预测值:"+predictValue);
        log.info("实际值:"+aDouble);
        result.put("predictValue", new BigDecimal(predictValue).setScale(2,4));
        result.put("predictMsg", aDouble>=predictValue?"判断结果高于预测":"判断结果低于预测");
        return result;
    }

    private double statisticsIndex(BiIndexType type, BiOperatedData store){
        JSONObject result = new JSONObject();
        //查询半径1公里内的指标
        int distance = 1;
        BigDecimal lng = new BigDecimal(store.getLng());
        BigDecimal lat = new BigDecimal(store.getLat());
        String[] points = DistanceHelper.getNearbyDistance(lng, lat, distance);
        String cacheKey = "IndexValue" + Arrays.toString(points);
        JSONObject valuesMap = baseDataRecordCache.get(cacheKey);
        if (valuesMap == null) {
            BiIndexDataParam biIndexDataParam = new BiIndexDataParam();
            biIndexDataParam.setLng1(points[0]);
            biIndexDataParam.setLng2(points[1]);
            biIndexDataParam.setLat1(points[2]);
            biIndexDataParam.setLat2(points[3]);
            CurrentDataSourceContext.setDataSourceType(WdAnalysisConst.CLICKHOUSE_DATASOURCE);
            List<BiIndexData> biIndexData = biIndexDataMapper.selectListCustom(biIndexDataParam);
            CurrentDataSourceContext.clearDataSourceType();

            valuesMap = new JSONObject();
            for (BiIndexData b : biIndexData) {
                if (valuesMap.containsKey(b.getTypeId())) {
                    continue;
                }
                double value = Double.parseDouble(b.getValue());
                if(value > 0){
                    valuesMap.put(String.valueOf(b.getTypeId()), value);
                }
            }
            baseDataRecordCache.put(cacheKey, valuesMap,2*60*60L);
        }
        Object value = valuesMap.get(String.valueOf(type.getId()));
        if (value == null) {
            return 0;
        }
        return Double.parseDouble(String.valueOf(value));
    }
}
